package com.onion.utils.utils;

import sun.misc.BASE64Encoder;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.Random;

/**
 * 验证码生成工具类
 */
public class CaptchaUtils {

    private static final String[] CODE_SEQUENCE = new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "J", "K",
                                                               "L", "M", "N", "P", "Q", "R", "S", "T", "U", "V",
                                                               "W", "X", "Y", "Z", "1", "2", "3", "4", "5", "6",
                                                               "7", "8", "9", "0"};

    public static final String ONION_CAPTCHA_PREFIX = "onion.captcha:";

    /**
     * 生成验证码及其图片，并转码Base64
     * @param codeCount     验证码位数
     * @param imgHeight     图片高度
     * @return
     * @throws IOException
     */
    public static String[] drawImageWithBase64(int codeCount, int imgHeight) throws IOException {
        //测算并生成图片
        CaptchaImage captchaImage = measureAndDrawImage(codeCount, imgHeight);

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        try {
            ImageIO.write(captchaImage.getImage(), "jpg", outputStream);
            BASE64Encoder encoder = new BASE64Encoder();
            return new String[]{captchaImage.getCode(), encoder.encode(outputStream.toByteArray())};
        } finally {
            if (outputStream != null) {
                outputStream.close();
            }
        }
    }

    /**
     * 生成验证码及其图片文件
     * @param codeCount     验证码位数
     * @param imgHeight     图片高度
     * @return
     * @throws IOException
     */
    public static String drawImageWithFile(int codeCount, int imgHeight, String outFile) throws IOException {
        //测算并生成图片
        CaptchaImage captchaImage = measureAndDrawImage(codeCount, imgHeight);
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(outFile, false);
        } catch (FileNotFoundException e) {
            throw new RuntimeException("FileNotFound:" + outFile);
        }
        try {
            drawImageWithOS(codeCount, imgHeight, fos);
            return captchaImage.getCode();
        }finally {
            if(fos != null){
                fos.close();
            }
        }
    }

    /**
     * 验证码绘图
     * @param codeCount 验证码位数
     * @param imgHeight 图片高度
     * @param outputStream
     * @return
     * @throws IOException
     */
    public static String drawImageWithOS(int codeCount, int imgHeight, OutputStream outputStream) throws IOException {
        //测算并生成图片
        CaptchaImage captchaImage = measureAndDrawImage(codeCount, imgHeight);
        ImageIO.write(captchaImage.getImage(), "jpg", outputStream);
        return captchaImage.getCode();
    }

    /**
     * 测算并绘图
     * @param codeCount
     * @param imgHeight
     * @return
     * @throws IOException
     */
    public static CaptchaImage measureAndDrawImage(int codeCount, int imgHeight) throws IOException {
        int codeX = 20;
        int imgWidth = codeX * (codeCount + 2);
        int fontHeight = imgHeight - 10;
        int codeY = fontHeight - 2;
        return genCodeDrawImage(codeCount, imgWidth, imgHeight, fontHeight, codeX, codeY);
    }

    /**
     * 随机生成验证码并绘图
     * @param codeCount 验证码位数
     * @param imgWidth
     * @param imgHeight
     * @param fontHeight
     * @param codeX
     * @param codeY
     * @return
     */
    private static CaptchaImage genCodeDrawImage(int codeCount, int imgWidth, int imgHeight, int fontHeight,
                                                 int codeX, int codeY) {
        Random random = new Random();
        StringBuffer randomCode = new StringBuffer();
        for(int i=0; i<codeCount; i++){
            randomCode.append(CODE_SEQUENCE[random.nextInt(CODE_SEQUENCE.length)]);
        }
        String captchCode = randomCode.toString();
        BufferedImage bufferImg = drawBufferImage(captchCode, imgWidth, imgHeight, fontHeight, codeX, codeY);
        return new CaptchaImage(captchCode, bufferImg);
    }

    /**
     * 绘图生成BufferedImage
     * @param captchCode
     * @param imgWidth
     * @param imgHeight
     * @param fontHeight
     * @param codeX
     * @param codeY
     * @return
     */
    private static BufferedImage drawBufferImage(String captchCode, int imgWidth, int imgHeight, int fontHeight,
                                                 int codeX, int codeY) {
        // 定义图像buffer
        BufferedImage buffImg = new BufferedImage(imgWidth, imgHeight, BufferedImage.TYPE_INT_RGB);
        Graphics gd = buffImg.getGraphics();
        // 创建一个随机数生成器类
        Random random = new Random();
        // 将图像填充为白色
        gd.setColor(Color.WHITE);
        gd.fillRect(0, 0, imgWidth, imgHeight);

        // 创建字体，字体的大小应该根据图片的高度来定。
        Font font = new Font("Fixedsys", Font.BOLD, fontHeight);
        // 设置字体
        gd.setFont(font);

        // 画边框
        gd.setColor(Color.BLACK);
        gd.drawRect(0, 0, imgWidth - 1, imgHeight - 1);

        // 随机产生40条干扰线，使图象中的认证码不易被其它程序探测到。
        gd.setColor(Color.BLACK);
        for (int i = 0; i < 40; i++) {
            int x = random.nextInt(imgWidth);
            int y = random.nextInt(imgHeight);
            int xl = random.nextInt(12);
            int yl = random.nextInt(12);
            gd.drawLine(x, y, x + xl, y + yl);
        }

        // randomCode用于保存随机产生的验证码，以便用户登录后进行验证。
        int red = 0, green = 0, blue = 0;
        // 随机产生codeCount数字的验证码
        if(imgWidth - codeX * captchCode.length() <= 0){
            throw new RuntimeException(String.format("绘图参数出错：画布宽度[%d]<=字宽[%dx%d]", imgWidth, captchCode.length(), codeX));
        }
        int position = (imgWidth-codeX*captchCode.length()) / 6;
        for(int i=0; i<captchCode.length(); i++){
            //随机产生每个验证码的颜色值
            red = random.nextInt(255);
            green = random.nextInt(255);
            blue = random.nextInt(255);
            // 用随机产生的颜色将验证码绘制到图像中
            gd.setColor(new Color(red, green, blue));

            String charCode = captchCode.substring(i, i+1);
            gd.drawString(charCode, position*(i+1) + i*codeX, codeY);
        }
        return buffImg;
    }

    /**
     * 验证码图像
     */
    private static class CaptchaImage {

        private String code;
        private BufferedImage image;

        public CaptchaImage(String code, BufferedImage image) {
            this.code = code;
            this.image = image;
        }

        public String getCode() {
            return code;
        }

        public BufferedImage getImage() {
            return image;
        }

    }

    public static void main(String[] args){
        String outFile = "/Users/onion/test.jpg";
        try {
            CaptchaUtils.drawImageWithFile(4, 50, outFile);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}